/*
 * PersistentCacheSE.java
 *
 * Created on 05. August 2008, 16:25
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */
package org.nViron.Graffiti;

import instar.dg.Client.BaseClient;
import instar.dg.Common.ID;
import instar.dg.Debug.Debug;
import instar.dg.GraffitiCache.PersistentCache;
import instar.dg.Graffito.GraffitoBody;
import instar.dg.Graffito.GraffitoHeader;
import java.util.Hashtable;
import java.io.*;

/**
 *
 * @author Stefan
 */
public class PersistentCacheSE extends PersistentCache {

    protected String cacheDirectory = "/";
    final String indexFilename = "cache.idx";
    protected Hashtable popupBodiesInRange = new Hashtable();

    /** Creates a new instance of PersistentCacheSE */
    public PersistentCacheSE(BaseClient baseClient, String cacheDir, int maxHeaderSize, int maxBodySize) throws Exception {
        super(baseClient, maxHeaderSize, maxBodySize);
        cacheDirectory = cacheDir;
        checkDir();
        loadIndex();
    }

    protected void checkDir() throws Exception {

        File fconn = new File(cacheDirectory);
        // If no exception is thrown, then the URI is valid, but the file may or may not exist.
        if (!fconn.exists() && (!fconn.mkdir() || !fconn.setReadable(true) || !fconn.setWritable(true))) {
            throw new Exception("checkDir failed ");
        }
    }

    @Override
    protected void addBody(final GraffitoBody body) {
        GraffitoHeader gh = (GraffitoHeader) headerInRange.get(body.getId());
        if ((gh != null) && gh.isPopUp()) {
            Debug.out.println(System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] PersistentCacheMe.addBody('" + body.getId().toString() + "'): graffito is a popup -> graffito is stored in memory too");
            synchronized (popupBodiesInRange) {
                popupBodiesInRange.remove(body.getId());
                popupBodiesInRange.put(body.getId(), body);
            }
        }
        Thread t = new Thread() {

            @Override
            public void run() {
                try {
                    String filename = cacheDirectory + "B" + body.getId().toString() + ".pgc";
                    //#mdebug
                    long begin = System.currentTimeMillis();
                    Debug.out.println(System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] PersistentCacheMe.addBody('" + filename + "')");
                    //#enddebug
                    synchronized (bodyIndex) {
                        try {
                            File fconn = new File(filename);
                            if (fconn.exists() && !fconn.delete()) {
                                throw new Exception("delete failed ");
                            }
                            fconn.createNewFile();

                            FileOutputStream os = new FileOutputStream(fconn, false);
                            DataOutputStream dos = new DataOutputStream(os);
                            body.appendToDataOutput(dos);
                            dos.close();
                            os.close();
                        } catch (IOException ex) {
                            ex.printStackTrace();
                        }
                    }
                    //#debug
                    Debug.out.println(System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] PersistentCacheMe.addBody('" + filename + "') ends in " + (System.currentTimeMillis() - begin) + " ms.");
                } catch (Throwable t) {
                    //#debug
                    Debug.out.println(System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] PersistentCacheMe.addBody('') throws " + t.toString());
                    t.printStackTrace();
                }
            }
        };
        t.start();
    }

    @Override
    public GraffitoBody loadBody(ID id) {
        Debug.out.println(System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] PersistentCacheMe.loadBody('" + id.toString() + "')");
        GraffitoBody body = null;
        body = (GraffitoBody) popupBodiesInRange.get(id);
        if (body == null) {
                synchronized (bodyIndex) {

                    try {
                        String filename = cacheDirectory + "B" + id.toString() + ".pgc";
                        File fconn = new File(filename);
                        if (fconn.exists() && fconn.length() > 0) {
                            FileInputStream is = new FileInputStream(fconn);
                            DataInputStream dis = new DataInputStream(is);
                            body = new GraffitoBody();
                            // overwrite type id
                            dis.readInt();
                            body.readFromDataInput(dis);
                            dis.close();
                            is.close();
                        }
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }
                }
        }
        return body;
    }

    @Override
    public boolean containsBody(ID id) {
        Debug.out.println(System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] PersistentCacheMe.containsBody('" + id.toString() + "')");

        boolean ret = popupBodiesInRange.containsKey(id);
        if (ret == false) {
                synchronized (bodyIndex) {
                        String filename = cacheDirectory + "B" + id.toString() + ".pgc";
                        File fconn = new File(filename);
                        ret = fconn.exists();
                }
        }
        return ret;
    }

    @Override
    public GraffitoBody removeBody(ID id) {
        GraffitoBody body = null;
        long begin = System.currentTimeMillis();
        Debug.out.println(System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "]  PersistentCacheMe.removeBody( " + id.toString() + " ) begins");
        popupBodiesInRange.remove(id);
        try {
            synchronized (bodyIndex) {
                deleteFile(cacheDirectory + "B" + id.toString() + ".pgc");
//                FileConnection fconn = (FileConnection)Connector.open( cacheDirectory + "B" + id.toString() + ".pgc", Connector.READ_WRITE );
//                // If no exception is thrown, then the URI is valid, but the file may or may not exist.
//                if ( fconn.exists() ) {
//                    DataInputStream dis = fconn.openDataInputStream();
//                    body = readBodyFromDataInput(dis);
//                    fconn.delete();
//                }
//                fconn.close();
            }
        } catch (Exception ex) {
            Debug.out.println("removeBody throws " + ex.toString());
            ex.printStackTrace();
        }
        Debug.out.println(System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] PersistentCacheMe.removeBody( " + id.toString() + " ) ends in " + (System.currentTimeMillis() - begin) + " ms.");
        return body;
    }

    @Override
    protected GraffitoHeader loadHeader(ID id) {
        GraffitoHeader header = null;
        long begin = System.currentTimeMillis();
        String filename = cacheDirectory + "H" + id.toString() + ".pgc";
        synchronized (headerIndex) {

            try {
                Debug.out.println(System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] PersistentCacheMe.loadHeader('" + filename + "')");
                File fconn = new File(filename);
                if (fconn.exists() && fconn.length() > 0) {

                    FileInputStream is = new FileInputStream(fconn);
                    DataInputStream dis = new DataInputStream(is);
                    header = new GraffitoHeader();
                    // overwrite type id
                    dis.readInt();
                    header.readFromDataInput(dis);
                    dis.close();
                    is.close();
                    dis = null;
                } else {
                    headerIndex.remove(id);
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }

        // if header has been loaded and is a popup which body hasn'nt already been loaded to to popupBodiesInRange table -> load it!
        if ((header != null) && header.isPopUp() && containsBody(header.getId()) && !popupBodiesInRange.containsKey(header.getId())) {
            Debug.out.println(System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] PersistentCacheMe.loadHeader('" + id.toString() + "'): popup detected, which body is loaded but not in memory");
            GraffitoBody gb = loadBody(header.getId());
            if (gb != null) {
                popupBodiesInRange.put(header.getId(), gb);
            }
        }
        //#debug
        Debug.out.println(System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] PersistentCacheMe.loadHeader('" + filename + "') ends in " + (System.currentTimeMillis() - begin) + " ms.");
        return header;
    }

    @Override
    protected void loadHeaderAsync(final ID id, final LoadListener callback) {
        Thread t = new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                    GraffitoHeader gh = loadHeader(id);
                    if (callback != null) {
                        callback.graffitoHeaderLoaded(gh);
                    }
                } catch (Throwable t) {
                    //#debug
                    Debug.out.println(System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] PersistentCacheMe.loadHeaderAsync() throws " + t.toString());
                    t.printStackTrace();
                }
            }
        });
        t.start();
    }

    @Override
    protected void addHeader(final GraffitoHeader header) {
        Thread t = new Thread() {

            @Override
            public void run() {
                try {
                    String filename = cacheDirectory + "H" + header.getId().toString() + ".pgc";
                    //#mdebug
                    long begin = System.currentTimeMillis();
                    Debug.out.println(System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] PersistentCacheMe.addHeader('" + filename + "')");
                    //#enddebug
                    synchronized (headerIndex) {

                        try {
                            File fconn = new File(filename);
                            if (fconn.exists() && !fconn.delete()) {
                                throw new Exception("delete failed ");
                            }
                            fconn.createNewFile();

                            FileOutputStream os = new FileOutputStream(fconn, false);
                            DataOutputStream dos = new DataOutputStream(os);
                            header.appendToDataOutput(dos);
                            os.close();
                            dos.close();
                        } catch (IOException ex) {
                            ex.printStackTrace();
                        }
                    }

                    //#debug
                    Debug.out.println(System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] PersistentCacheMe.addHeader('" + filename + "') ends in " + (System.currentTimeMillis() - begin) + " ms.");
                } catch (Throwable t) {
                    //#debug
                    Debug.out.println(System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] PersistentCacheMe.loadHeaderAsync() throws " + t.toString());
                    t.printStackTrace();
                }
            }
        };
        t.start();
    }

    @Override
    protected GraffitoHeader removeHeaderInRange(ID id) {
        Debug.out.println(System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] PersistentCacheMe.removeHeaderInRange('" + id.toString() + "')");
        popupBodiesInRange.remove(id);
        return super.removeHeaderInRange(id);
    }

    public static boolean isSupported() {
        return System.getProperty("microedition.io.file.FileConnection.version") != null;
    }

    private void loadIndex() {
        long begin = System.currentTimeMillis();
        Debug.out.println(System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] PersistentCacheMe.loadIndex()");
        headerIndex.clear();
        bodyIndex.clear();
        File fconn = null;
        try {
            String filename = cacheDirectory + indexFilename;
            fconn = new File(filename);
            // If no exception is thrown, then the URI is valid, but the file may or may not exist.
            if (fconn.exists()) {
                Debug.out.println("... loading index");
                FileInputStream is = new FileInputStream(fconn);
                DataInputStream dis = new DataInputStream(is);
                if (fconn.length() > 0) {
                    int hcnt = dis.readInt();
                    for (int i = 0; i < hcnt; i++) {
                        Header h = new Header();
                        h.readFromDataInput(dis);
                        headerIndex.add(h);
                    }

                    int bcnt = dis.readInt();
                    for (int i = 0; i < bcnt; i++) {
                        Body b = new Body();
                        b.readFromDataInput(dis);
                        bodyIndex.add(b);
                    }
                }
                is.close();
                dis.close();
            } else {
                Debug.out.println("No index file found!!!");
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
        }
        Debug.out.println(System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] PersistentCacheMe.loadIndex() ends in " + (System.currentTimeMillis() - begin) + " ms.");
    }

    private void saveIndex() {
        Debug.out.println("saveIndex()");
        try {
            String filename = cacheDirectory + indexFilename;
            File fconn = new File(filename);
            if (fconn.exists()) {
                fconn.delete();
            }

            if ((headerIndex.getCount() > 0) || (bodyIndex.getCount() > 0)) {
                fconn.createNewFile();
                FileOutputStream os = new FileOutputStream(fconn);
                DataOutputStream dos = new DataOutputStream(os);
                Object o = null;

                synchronized (headerIndex) {
                    // write number of header
                    dos.writeInt(headerIndex.getCount());
                    Header h = null;
                    int size = headerIndex.getSize();
                    for (int i = 0; i < size; i++) {
                        o = headerIndex.get(i);
                        if (o != null) {
                            h = (Header) o;
                            h.appendToDataOutput(dos);
                        }
                    }
                }

                synchronized (bodyIndex) {
                    dos.writeInt(bodyIndex.getCount());
                    Body b = null;
                    int size = bodyIndex.getSize();
                    for (int i = 0; i < size; i++) {
                        o = bodyIndex.get(i);
                        if (o != null) {
                            b = (Body) o;
                            b.appendToDataOutput(dos);
                        }
                    }
                }
                dos.close();
                os.close();
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
    private final Object sync = new Object();

    @Override
    public void stop() {
        Thread t = new Thread() {

            @Override
            public void run() {
                try {
                    saveIndex();
                    synchronized (sync) {
                        sync.notifyAll();
                    }
                } catch (Throwable t) {
                    //#debug
                    Debug.out.println(System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] PersistentCacheMe.stop() throws " + t.toString());
                    t.printStackTrace();
                }
            }
        };
        t.start();
        try {
            synchronized (sync) {
                sync.wait();
            }
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        super.stop();
    }

    protected void deleteFile(String filename) {
        try {
            File fconn = new File(filename);
            if (fconn.exists()) {
                fconn.delete();
            }
        } catch (Throwable ex) {
            Debug.out.println("PersistentCacheME throws " + ex.toString());
            ex.printStackTrace();
        }
    }

    @Override
    public void clear() {
        super.clear();
        popupBodiesInRange.clear();

        Thread t = new Thread() {

            @Override
            public void run() {
                try {
                    File fconn = new File(cacheDirectory);
                    // If no exception is thrown, then the URI is valid, but the file may or may not exist.
                    if (fconn.exists() && fconn.isDirectory()) {
                        String[] fs = fconn.list();
                        for (int i=0; i< fs.length; i++) {
                            String filename = cacheDirectory + "/" + ((String) fs[i]);
                            deleteFile(filename);
                        }
//                        Debug.out.println( "delete " + cacheDirectory );
//                        fconn.delete();
                    }
                } catch (Throwable t) {
                    //#debug
                    Debug.out.println(System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] PersistentCacheMe.clear() throws " + t.toString());
                    t.printStackTrace();
                }
            }
        };
        t.start();
    }
}
